package com.michaelbulava.TFS.vcs.internal.TFSFiles;

import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.fileTypes.FileTypeManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vcs.FilePath;
import com.intellij.openapi.vcs.actions.VcsContextFactory;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.encoding.EncodingManager;
import com.intellij.openapi.vfs.encoding.EncodingProjectManager;
import com.intellij.util.PathUtil;
import com.michaelbulava.TFS.log.FileLogger;
import com.michaelbulava.TFS.vcs.exceptions.UnableToCreateTFSFilePathException;
import com.michaelbulava.TFS.vcs.utils.TFSFileUtil;
import com.microsoft.tfs.core.clients.versioncontrol.path.ServerPath;
import com.microsoft.tfs.core.clients.versioncontrol.soapextensions.*;
import com.microsoft.tfs.util.Check;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.File;
import java.io.FileNotFoundException;
import java.nio.charset.Charset;
import java.util.UUID;

/**
 * Created by MichaelA on 12/22/2015.
 */
public class TFSFilePath implements FilePath {
    private static final Logger log = FileLogger.getInstance(TFSFilePath.class);

    private File localFile;
    //private ExtendedItem extendedItem;
    private final Workspace workspace;
    private UUID cacheId;

    public TFSFilePath(Workspace workspace, ExtendedItem extendedItem){
        Check.notNull(workspace, "workspace");
        Check.notNull(extendedItem, "extendedItem");

        this.workspace = workspace;
        this.cacheId = TFSFileUtil.performCache(extendedItem, workspace);

        initializeFromExtendedItem();
    }

    public TFSFilePath(Workspace workspace, UUID cachedExtendedItemId){
        Check.notNull(workspace, "workspace");
        Check.notNull(cachedExtendedItemId, "cachedExtendedItemId");
        this.workspace = workspace;
        this.cacheId = cachedExtendedItemId;
        initializeFromExtendedItem();
    }

    public TFSFilePath(Workspace workspace, String serverOrLocalPath) throws UnableToCreateTFSFilePathException {
        Check.notNull(serverOrLocalPath, "serverOrLocalPath");
        Check.notNull(workspace, "workspace");

        this.workspace = workspace;

        initializeFromAPath(serverOrLocalPath);
    }

    public TFSFilePath(Workspace workspace, FilePath file) throws UnableToCreateTFSFilePathException {
        Check.notNull(workspace, "workspace");
        Check.notNull(file, "file");
        Check.isTrue(!(file instanceof  TFSFilePath), "file cannot already be a TFSFilePath");

        this.workspace = workspace;

        initializeFromAPath(file.getPresentableUrl());
    }

    public TFSFilePath(Workspace workspace, PendingChange pendingChange) throws UnableToCreateTFSFilePathException {
        Check.notNull(workspace, "workspace");
        Check.notNull(pendingChange, "pendingChange");

        this.workspace = workspace;
        initializeFromAPendingChange(pendingChange);
    }

    private void initializeFromAPendingChange(PendingChange pendingChange) throws UnableToCreateTFSFilePathException {
        ExtendedItem item = null;
        if (pendingChange.getItemID() > 0)
            item = TFSFileUtil.HuntExtendedItemById(workspace, pendingChange.getItemID());
        if (item != null) {
            this.cacheId = TFSFileUtil.performCache(item, workspace);
            this.initializeFromExtendedItem();
            return;
        }
        throw new UnableToCreateTFSFilePathException("Could not lookup Extended Item by Id");
    }

    private void initializeFromAPath(String serverOrLocalPath) throws UnableToCreateTFSFilePathException {
        if (!ServerPath.isServerPath(serverOrLocalPath)){
            WorkingFolder workingFolder = TFSFileUtil.getWorkingFolder(serverOrLocalPath, workspace);
            if (workingFolder != null){
                serverOrLocalPath = workingFolder.getServerItem();
            }
        } else{

            String localPath = this.workspace.translateServerPathToLocalPath(serverOrLocalPath).getTranslatedPath();
        }
        this.cacheId = TFSFileUtil.getCachedExtendedItem(serverOrLocalPath, workspace);
        if (this.cacheId == null)
            throw new UnableToCreateTFSFilePathException(String.format("Unable to lookup serverpath [%s]", serverOrLocalPath));
//        this.extendedItem = TFSFileUtil.getExtendedItem(serverOrLocalPath);
//
//
//        if (items.length == 0){
//            PendingSet ps =  this.workspace.getPendingChanges(new String[] { serverOrLocalPath }
//                    , RecursionType.NONE, true);
//            if (ps != null){
//                PendingChange[] pendingChanges =ps.getPendingChanges();
//                if (pendingChanges != null){
//                    for(int i = 0; i < pendingChanges.length; i++){
//                        initializeFromAPendingChange(pendingChanges[i]);
//                        return;
//                    }
//                }
//            }
//        }
//        if (items.length == 1)
//            this.extendedItem = items[0];
//        else
//            throw new UnableToCreateTFSFilePathException("Unable to lookup serverpath");
        initializeFromExtendedItem();
    }




    private void initializeFromExtendedItem(){
        ExtendedItem item = getExtendedItem();
        String filePath = item.getLocalItem();
        if (!StringUtil.isNotEmpty(filePath)){
            //If the filePath it null the file most likely doesn't exist locally
            //So let TFS translate from Server to local path
            PathTranslation translatedPath = workspace
                    .translateServerPathToLocalPath(item.getTargetServerItem());
            filePath = translatedPath.getTranslatedPath();
        }

        localFile = new File(filePath);
    }

    /**
     * @return a virtual file that corresponds to this path, or null if the virtual file is no more valid.
     */
    @Nullable
    @Override
    public VirtualFile getVirtualFile() {
        return LocalFileSystem.getInstance().findFileByIoFile(localFile);
    }

    /**
     * @return the virtual file that corresponds to the parent file path, or null if the virtual file is no more valid.
     */
    @Nullable
    @Override
    public VirtualFile getVirtualFileParent() {
        if (localFile.exists())
            return getVirtualFile().getParent();
        else
            return LocalFileSystem.getInstance().findFileByIoFile(localFile.getParentFile());
    }

    /**
     * @return the {@link File} that corresponds to the path. The path might be non-existent or not local.
     * @see #isNonLocal()
     */
    @NotNull
    @Override
    public File getIOFile() {
        return localFile;
    }

    /**
     * @return the file name (without directory component)
     */
    @NotNull
    @Override
    public String getName() {
        return localFile.getName();
    }

    /**
     * @return the path to the file in the format suitable for displaying in the UI,
     * e.g. for local file it is the path to this file with system separators.
     */
    @Override
    public String getPresentableUrl() {
        return localFile.getPath();
    }

    /**
     * @deprecated to remove in IDEA 16.
     * Use {@link FileDocumentManager#getDocument(VirtualFile)} directly.
     */
    @Nullable
    @Override
    public Document getDocument() {
        VirtualFile file = getVirtualFile();
        if (file == null || file.getFileType().isBinary()) {
            return null;
        }
        return FileDocumentManager.getInstance().getDocument(file);
        //return FileDocumentManager.getInstance().getDocument(getVirtualFile());
    }

    @NotNull
    @Override
    public Charset getCharset() {
        return getCharset(null);
    }

    /**
     * @param project
     * @return the character set, considering the project settings and the virtual file corresponding to this FilePath (if it exists).
     */
    @NotNull
    @Override
    public Charset getCharset(Project project) {
        VirtualFile file = getVirtualFile();
        String path = localFile.getPath();
        while ((file == null || !file.isValid()) && !path.isEmpty()) {
            path = PathUtil.getParentPath(path);
            file = LocalFileSystem.getInstance().findFileByPath(path);
        }
        if (file != null) {
            return file.getCharset();
        }
        EncodingManager e = project == null ? EncodingManager.getInstance() : EncodingProjectManager.getInstance(project);
        return e.getDefaultCharset();
    }

    /**
     * @return the type of the file.
     */
    @NotNull
    @Override
    public FileType getFileType() {
        VirtualFile file = getVirtualFile();
        FileTypeManager manager = FileTypeManager.getInstance();
        return file != null ? manager.getFileTypeByFile(file) : manager.getFileTypeByFileName(getName());
    }

    /**
     * @deprecated to remove in IDEA 16.
     * Use {@code com.intellij.openapi.vfs.VfsUtil#findFileByPath} or {@code com.intellij.openapi.vfs.LocalFileSystem#findFileByPath} instead.
     */
    @Override
    public void refresh() {
        ExtendedItem[] items = workspace.getExtendedItems(localFile.getPath(), DeletedState.ANY,
                ItemType.ANY);
        if (items.length == 1)
            this.cacheId = TFSFileUtil.performCache(items[0], workspace);
        else
            throw new RuntimeException(String.format("Expected to locate 1 parent item and found %d", items.length));

        initializeFromExtendedItem();

        this.mSourceLocalFilePath = null;
        this.mTargetLocalFilePath = null;
    }

    /**
     * @deprecated to remove in IDEA 16.
     * Use {@code com.intellij.openapi.vfs.LocalFileSystem#refreshAndFindFileByPath} instead.
     */
    @Override
    public void hardRefresh() {
        refresh();
    }

    /**
     * @return the path to the file represented by this file path in the system-independent format.
     */
    @NotNull
    @Override
    public String getPath() {
        if (localFile != null)
            return localFile.getPath();
        return getCachedExtendedItem().getTargetServerItem();
    }

    /**
     * @return true if the path represents the directory
     */
    @Override
    public boolean isDirectory() {
        return getCachedExtendedItem().getItemType() == ItemType.FOLDER;
    }

    /**
     * Check if the provided file is an ancestor of the current file.
     *
     * @param parent a possible parent
     * @param strict if false, the method also returns true if files are equal
     * @return true if {@code this} file is ancestor of the {@code parent}.
     */
    @Override
    public boolean isUnder(@NotNull FilePath parent, boolean strict) {
//        if (parent instanceof  TFSFilePath){
//            ServerPath.isChild(((TFSFilePath)parent).getExtendedItem().getTargetServerItem(),
//                    getExtendedItem().getTargetServerItem());
//        }
        return FileUtil.isAncestor(parent.getIOFile(), getIOFile(), strict);
    }

    /**
     * @return the parent path or null if there is no parent of this file.
     */
    @Nullable
    @Override
    public FilePath getParentPath() {
        ExtendedItem[] items = workspace.getExtendedItems(ServerPath.getParent(getCachedExtendedItem().getTargetServerItem()),
                DeletedState.ANY, ItemType.ANY);
        if (items.length == 1)
            return new TFSFilePath(workspace, items[0]);
        else
            throw new RuntimeException(String.format("Expected to locate 1 parent item and found %d", items.length));
    }

    /**
     * @return true if the path does not represents a file in the local file system
     */
    @Override
    public boolean isNonLocal() {
        return !localFile.exists();
    }

    public ExtendedItem getExtendedItem(){
        return this.getCachedExtendedItem();
    }


    private FilePath mTargetLocalFilePath = null;
    private FilePath mSourceLocalFilePath = null;

    public FilePath getTargetLocalFilePath(){
        if (mTargetLocalFilePath == null){
            String translatedLocalPath = workspace.translateServerPathToLocalPath(getCachedExtendedItem()
                    .getTargetServerItem()).getTranslatedPath();
            File file = new File(translatedLocalPath);
            mTargetLocalFilePath = VcsContextFactory.SERVICE.getInstance().createFilePathOn(file
                    , getCachedExtendedItem().getItemType() == ItemType.FOLDER);
        }
        return mTargetLocalFilePath;
    }

    public FilePath getSourceLocalFilePath(){
        if (mSourceLocalFilePath == null){
            ExtendedItem item = getCachedExtendedItem();
            String translatedLocalPath = workspace.translateServerPathToLocalPath(item.getSourceServerItem())
                    .getTranslatedPath();
            File file = new File(translatedLocalPath);
            mSourceLocalFilePath = VcsContextFactory.SERVICE.getInstance().createFilePathOn(file
                    , item.getItemType() == ItemType.FOLDER);
        }
        return mSourceLocalFilePath;
    }


    public int getLocalVersion(){
        return getCachedExtendedItem().getLocalVersion();
    }

    public int getLatestVersion(){
        return getCachedExtendedItem().getLatestVersion();
    }

    public int getItemId(){
        return getCachedExtendedItem().getItemID();
    }

    private ExtendedItem getCachedExtendedItem(){
        try{
            return TFSFileUtil.readFromCache(this.cacheId);
        } catch(FileNotFoundException tr){
            refresh();
            try {
                return TFSFileUtil.readFromCache(this.cacheId);
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    private void test(){

    }
}
